<?php
namespace app\common\logic;

use fast\AipHttpClient;
use IEXBase\TronAPI\Exception\TronException;
use IEXBase\TronAPI\Tron;
use IEXBase\TronAPI\TronAddress;
use think\Exception;
use think\Log;
use function Matrix\add;

/**
 * TODO：该代码希望你至少知道一些区块链的基础概念，如地址、公钥、私钥、合约等。如不清楚，请先查阅【至少】一次官方文档，谢谢！！！
 * TODO：文档地址：https://cn.developers.tron.network/reference  术语地址： https://cn.developers.tron.network/docs/glossary
 *
 * 编码环境win10  php7.3 linux请注意目录大小写的区分
 * 如果你觉得代码不错 对您的项目有帮助 可以请编码小哥喝一杯卡布奇洛 TKauo6SnhLNyTDvkVByR13cR7S5xnq3yT4 谢谢！
 *
 * Class TrcLogic
 * @package app\common\logic
 */
class TronLogic
{
    /*** @var string $NET 网络节点 ，上线后请自行切换到主链（https://api.trongrid.io） */
    private $NET = "https://api.shasta.trongrid.io"; // Shasta测试网

    /*** @var string $OWNER_ADDRESS 平台账户 创建地址激活需要提供一个平台地址 归集时将其他地址的代币归集到该地址中*/
    private $OWNER_ADDRESS = "TMuvMdGYH8mcfRNQZrHGNss6mXPFxqmKWC";

    /*** @var string $OWNER_PRIVATE_KEY 平台私钥 转账等操作时签名需要用到*/
    private $OWNER_PRIVATE_KEY = "05317011d91421b3c...9baeea965b809fb7597c3525f555b7";

    private $HEADER = [
        "Content-Type"=> "application/json",
        "TRON-PRO-API-KEY"=> "TRON-PRO-API-KEY 请查阅官方文档申请"  // 目前使用Trongrid请求Shasta/Nile测试网不需要设置API Key。
    ];

    /*** @var string $CONTRACT_ADDRESS 合约地址 只要能在波场浏览器中搜索到的trc20合约代币都可以转账操作*/
    private $CONTRACT_ADDRESS = [
        "USDT" => "TMuvMdGYH8mcfRNQZrHGNss6mXPFxqmKWC",
        "LK" => "TJiDo8aBQCQmEKH7gyso7ys1qxGJzEgvRo" // 我自己的测试合约
    ];

    private $tron = null;

    /**
     * 实例一个tron对象
     * TronLogic constructor.
     * @throws TronException
     */
    public function __construct()
    {
        $fullNode = new \IEXBase\TronAPI\Provider\HttpProvider($this->NET);
        $solidityNode = new \IEXBase\TronAPI\Provider\HttpProvider($this->NET);
        $eventServer = new \IEXBase\TronAPI\Provider\HttpProvider($this->NET);

        try {
            $this->tron = new \IEXBase\TronAPI\Tron($fullNode, $solidityNode, $eventServer);

            // 是否使用离线签名  默认是  新版本中主链网络已不支持线上签名 只能线下签名  详见官方文档 https://cn.developers.tron.network/reference#%E7%AD%BE%E7%BD%B2%E7%9A%84%E4%BA%A4%E6%98%93%E6%95%B0%E6%8D%AE
            $this->tron->setIsOnlineSign(false);
        } catch (\IEXBase\TronAPI\Exception\TronException $e) {
            exit($e->getMessage());
        }
    }

    /**
     * 生成地址并激活
     *
     * 波场创建地址需要激活才能使用 使用该方法创建地址需要平台账户中有trx余额  否则创建的地址是未激活的
     * 也可以用其他激活有余额的地址给这个地址转账来激活这个地址
     * @return array
     * @throws \IEXBase\TronAPI\Exception\TronException
     * @throws \think\Exception
     */
    public function createAddress(){
        # 新建一个账户

        $account = $this->tron->generateAddress();

        # 地址
        $address = $account->getAddress(true);

        # 私钥
        $privateKey = $account->getPrivateKey();

        # 公钥
        $publicKey = $account->getPublicKey();

        # 激活地址  $result['result'] = 1 \ $result['txId'] = "xxxxxxxxxx"
        $result =  $this->activeAddress($this->tron, $address);

        return compact("address","privateKey","publicKey","result");
    }

    /**
     * 激活地址
     * @param \IEXBase\TronAPI\Tron|null $tron
     * @param string $address
     * @return bool|mixed
     * @throws \think\Exception
     */
    private function activeAddress(?\IEXBase\TronAPI\Tron $tron, string $address)
    {
        $url = $this->NET . "/wallet/createaccount";

        $postData = [
            'owner_address' => $tron->toHex($this->OWNER_ADDRESS),
            'account_address' => $tron->toHex($address),
            'visible' => false
        ];

        # 创建一个交易
        $transactionResult = jsonDecode(post($url,jsonEncode($postData)));
        if (empty($transactionResult)) return false;

        # 签名并且广播
        return $this->signAndBroadcast($tron,$transactionResult,$this->OWNER_PRIVATE_KEY);
    }


    /**
     * TRC20转账
     * @param $to
     * @param $amount
     * @param $symbol
     * @return mixed
     * @throws Exception
     */
    public function trc20Transaction($to, $amount, $symbol = "LK"){
        $url = $this->NET . "/wallet/triggersmartcontract";
        $_to = fill0($this->tron->toHex($to));
        $_amount = fill0(decToHex($amount * 10 ** 6,false));

        $postData = [
            'contract_address' => $this->tron->toHex($this->CONTRACT_ADDRESS[$symbol]),
            'owner_address' => $this->tron->toHex($this->OWNER_ADDRESS),
            'function_selector' => "transfer(address,uint256)",
            'parameter' => $_to . $_amount,
            'call_value' => 0,
            'fee_limit' => 40000000
        ];

        $http = new AipHttpClient($this->HEADER);
        $transactionResult = $http->post($url, jsonEncode($postData));

        if ($transactionResult['code'] != 200) return false;

        $content = jsonDecode($transactionResult['content']);

        return $this->signAndBroadcast($this->tron, $content['transaction'], $this->OWNER_PRIVATE_KEY);
    }


    /**
     * TRX转账
     *
     * 平台账户转出  可将from设为变量（setPrivateKey设置为from地址的私钥） 实现其他账户两两转账
     * @param $to
     * @param $amount
     * @return mixed
     * @throws Exception
     */
    public function trxTransaction($to, $amount){
        try {
            $this->tron->setPrivateKey($this->OWNER_PRIVATE_KEY); // 设置转出账户的私钥
            return $result = $this->tron->sendTransaction($to,(float)$amount,"",$this->OWNER_ADDRESS);
        }catch (\IEXBase\TronAPI\Exception\TronException $e){
            throw new Exception($e->getMessage());
        }
    }


    /**
     * 获取TRX账户余额
     * @param $address
     * @return float
     */
    public function getAccount($address){
        $url = $this->NET . "/wallet/getaccount";
        $postData = [
            "address" => $address,
            "visible" => true
        ];
        $http = new AipHttpClient($this->HEADER);
        $result  = $http->post($url,jsonEncode($postData));
        $content = jsonDecode($result['content']);
        if (!isset($content['balance'])){
            $balance = 0;
        }else{
            $balance = $this->tron->fromTron($content['balance']);
        }
        return $balance;
    }

    /**
     * 获取Trc20余额
     * @param $address
     * @param $symbol
     * @return float|int
     */
    public function getTrc20Balance($address,$symbol = "LK"){
        $url = $this->NET . "/wallet/triggersmartcontract";
        $_address = fill0($this->tron->toHex($address));

        $postData = [
            'contract_address' => $this->tron->toHex($this->CONTRACT_ADDRESS[$symbol]),
            'owner_address' => $this->tron->toHex($this->OWNER_ADDRESS),
            'function_selector' => "balanceOf(address)",
            'parameter' => $_address
        ];

        $http = new AipHttpClient($this->HEADER);
        $transactionResult = $http->post($url, jsonEncode($postData));
        $content = jsonDecode($transactionResult['content']);
        $constant_result = $content['constant_result'][0];
        # 移除前面的000占位符
        $s = preg_replace('/^0*/', '', $constant_result);

        // 为空 0 || 16进制转10进制后转换为一个trx单位的数字
        return empty($s) ? 0 : $this->tron->fromTron(hexToDec($s));
    }


    /**
     * 归集
     * @param $address 发送者地址
     * @param $privateKey 发送者私钥
     * @param $symbol 代币
     * @return array|void
     * @throws Exception
     */
    public function collectionTrc20($address,$privateKey,$symbol = "LK"){
        // 获取Trc20余额
        $trc20Balance = $this->getTrc20Balance($address,$symbol);
        if ($trc20Balance <= 0) return [
            "status" => -1,
            "msg" => "代币余额不足"
        ];
        // 获取Trx余额
        $trxBalance = $this->getAccount($address);

        if ($trxBalance < 5){
            // 充值手续费
            $result = $this->trxTransaction($address,5);
            return [
                "status" => 0,
                "msg" => 'TRX不足，已充值，下次归集',
                "result" => $result
            ];
        }

        // 归集 转账
        $result = $this->collectionTrc20Transaction($address,$privateKey,$this->OWNER_ADDRESS,$trc20Balance,$symbol);


        return [
            "status" => 1,
            "msg" => "已完成归集，数量：{$trc20Balance}",
            "result" => $result
        ];
    }

    /**
     * 归集账户Trc20转账
     * @param $fromAddress 发送者地址
     * @param $privateKey 发送者私钥
     * @param $toAddress 接受者地址
     * @param $amount 数量
     * @param string $symbol 币种
     * @return bool|mixed
     * @throws Exception
     */
    private function collectionTrc20Transaction($fromAddress,$privateKey,$toAddress,$amount,$symbol = "LK"){
        $url = $this->NET . "/wallet/triggersmartcontract";
        $_to = fill0($this->tron->toHex($toAddress));
        // 6 是$symbol的精度，有些币种精度为6  有些为18,这里需要将币转为相对精度的科学计数，在转为16进制，然后在前面补0
        $_amount = fill0(decToHex($amount * 10 ** 6,false));
        $postData = [
            'contract_address' => $this->tron->toHex($this->CONTRACT_ADDRESS[$symbol]),
            'owner_address' => $this->tron->toHex($fromAddress),
            'function_selector' => "transfer(address,uint256)",
            'parameter' => $_to . $_amount,
            'call_value' => 0,
            'fee_limit' => 40000000
        ];

        $http = new AipHttpClient($this->HEADER);
        $transactionResult = $http->post($url, jsonEncode($postData));

        if ($transactionResult['code'] != 200) return false;

        $content = jsonDecode($transactionResult['content']);

        return $this->signAndBroadcast($this->tron, $content['transaction'], $privateKey);
    }


    /**
     * 按交易哈希查询交易
     * @param $txId
     * @return bool
     */
    public function getTransactionById($txId){
        $url = $this->NET . "/walletsolidity/gettransactionbyid";
        $postData = [
            "value" => $txId
        ];
        $http = new AipHttpClient($this->HEADER);
        $result = $http->post($url, jsonEncode($postData));

        if ($result['code'] != 200) return false;
        $content = jsonDecode($result['content']);
        return "SUCCESS" == $content['ret'][0]['contractRet'];
    }

    /**
     * 获取地址历史交易记录
     * @param $address 地址
     * @param $symbol 代币
     * @param int $block_number_timestamp 最新区块
     * @return array
     */
    public function trc20TransactionsList($address,$symbol,$block_number_timestamp = 0){
        // 更新到主链需切换api.trongrid.io
//        $url = "https://api.shasta.trongrid.io/v1/accounts/{$address}/transactions/trc20";
        $url = $this->NET . "/v1/accounts/{$address}/transactions/trc20";

        $params = [
            "only_confirmed" => "true",
            "contract_address" => $this->CONTRACT_ADDRESS[$symbol],
            "only_to" => "true",
            "min_timestamp" => $block_number_timestamp
        ];

        $http = new AipHttpClient($this->HEADER);
        return $http->get($url,$params);
    }

    /**
     * 签名并且广播
     * @param Tron $tron
     * @param $transaction
     * @param $privateKey
     * @return mixed
     * @throws Exception
     */
    private  function signAndBroadcast($tron,$transaction,$privateKey){
        $tron->setPrivateKey($privateKey);
        try {
            # 签名
            $signTransaction = $tron->signTransaction($transaction);
        }catch (TronException $e){
            throw new \think\Exception($e->getMessage());
        }
        # 广播签名后的事务
        return $tron->sendRawTransaction($signTransaction);
    }


    /**
     * 验证是否是一个波场地址
     * @param $address
     * @return array
     * @throws TronException
     */
    public function validateAddress($address){
        return $this->tron->validateAddress($address);
    }

}